using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Threading;

namespace Dida
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
	public class ParseMethodAttribute : Attribute
	{
	}

	public class ParsersRegister
	{
		delegate object ParseDelegate(string src);
		Dictionary<Type, ParseDelegate> parsers = new Dictionary<Type, ParseDelegate>();

		ReaderWriterLock rwLock = new ReaderWriterLock();

		public static readonly ParsersRegister Default;
		
		static ParsersRegister()
		{
			Default = new ParsersRegister();
			Default.RegisterCommonParsers();
		}

		/// <summary>
		/// Registers the most common parsers.
		/// </summary>
		/// <remarks>
		/// Parsers for the following types are registered:
		/// <list type="bullet">
		/// <item>float</item>
		/// <item>int</item>
		/// <item>string</item>
		/// </list>
		/// </remarks>
		public void RegisterCommonParsers()
		{
			rwLock.AcquireWriterLock(-1);

			ParseDelegate floatParser =
				delegate(string src)
				{
					return float.Parse(src, CultureInfo.InvariantCulture);
				};
			parsers.Add(typeof(float), floatParser);

			ParseDelegate intParser = 
				delegate(string src)
				{
					return int.Parse(src, CultureInfo.InvariantCulture);
				};
			parsers.Add(typeof(int), intParser);

			ParseDelegate stringParser =
				delegate(string src)
				{
					return src;
				};
			parsers.Add(typeof(string), stringParser);

			ParseDelegate charParser =
				delegate(string src)
				{
					return char.Parse(src);
				};
			parsers.Add(typeof(char), charParser);

			parsers.Add(typeof(bool), BoolParser);

			rwLock.ReleaseWriterLock();
		}

		private static object BoolParser(string s)
		{
			if (s == "true")
				return true;
			else if (s == "false")
				return false;
			else
			{
				throw new Exception("A boolean value should be either \"true\" or \"false\".");
			}
		}

		public void ScanType(Type type)
		{
			rwLock.AcquireWriterLock(-1);

			Debug.Assert(!parsers.ContainsKey(type), "A parser for this type has already been registered");

#if DEBUG
			bool parserFound = false;
#endif

			MethodInfo[] methods = type.GetMethods();
			foreach (MethodInfo method in methods)
			{
				object[] customAttrs = method.GetCustomAttributes(
					typeof(ParseMethodAttribute),true);
				if (customAttrs.Length != 0)
				{
					Debug.Assert(method.GetParameters().Length == 1 &&
						method.GetParameters()[0].ParameterType == typeof(string) &&
						method.ReturnType != typeof(void),						
						"The method " + type.FullName + "." + method.Name +
						" can't be used as a parse method. It should take 1 argument of type string, " + 
						"and should have a return value");
					Debug.Assert(method.IsStatic, "A parser method should be static");

#if DEBUG
					Debug.Assert(!parserFound, "A type should only contain one parser method");
					parserFound = true;
#endif

					if (method.ReturnType.IsValueType)
					{
						ParseDelegate parser = WrapValueTypeParser(type, method);
						parsers.Add(type, parser);
					}
					else
					{
						ParseDelegate parser = (ParseDelegate)Delegate.CreateDelegate(
							typeof(ParseDelegate), method);
						parsers.Add(type, parser);
					}
				}
			}

#if DEBUG
			Debug.Assert(parserFound, "No parser found on the type " + type.FullName);
#endif

			rwLock.ReleaseWriterLock();
		}

		private static ParseDelegate WrapValueTypeParser(Type type, MethodInfo method)
		{
			Debug.Assert(method.GetParameters().Length == 1);

			DynamicMethod wrapper = new DynamicMethod("", typeof(object),
				new Type[] { typeof(string) }, type);

			ILGenerator ilGen = wrapper.GetILGenerator();
			ilGen.Emit(OpCodes.Ldarg_0);
			ilGen.Emit(OpCodes.Call, method);
			ilGen.Emit(OpCodes.Box, method.ReturnType);
			ilGen.Emit(OpCodes.Ret);

			return (ParseDelegate)wrapper.CreateDelegate(typeof(ParseDelegate));
		}

		/// <summary>
		/// Parses the given string into an instance of the given type.
		/// </summary>
		/// <param name="type">The type the object should be parsed into.</param>
		/// <param name="src">The string containing the string representation of the object.</param>
		/// <returns>The parsed object.</returns>
		public object Parse(Type type,string src)
		{
			rwLock.AcquireReaderLock(-1);

			ParseDelegate parser;
			if (parsers.TryGetValue(type, out parser))
			{
				object ret = parser(src);
				
				rwLock.ReleaseReaderLock();
				return ret;
			}
			
			rwLock.ReleaseReaderLock();
			
			if (type.IsArray)
				return ParseArray(type, src);

			throw new ParserNotFoundException(type);
		}

		private object ParseArray(Type type, string src)
		{
			Debug.Assert(type.IsArray);

			// split the src array by commas, except for those parts 
			// that are wrapped in brackets

			List<string> elements = new List<string>();
			string element;

			int bracketLevel = 0;
			int substringStart = 0;
			for (int i = 0; i < src.Length; i++)
			{
				if (src[i] == ',' && bracketLevel == 0)
				{
					element = src.Substring(substringStart, i - substringStart);
					elements.Add(element);

					substringStart = i + 1;
				}
				else if (src[i] == '(')
				{
					bracketLevel++;
				}
				else if (src[i] == ')')
				{
					bracketLevel--;
				}
			}

			element = src.Substring(substringStart, src.Length - substringStart);
			if (substringStart == 0 && element.Trim() != "")    // special case for an empty array
			{
				elements.Add(element);
			}

			Array ret = (Array)Activator.CreateInstance(type,elements.Count);
			for (int i = 0; i < elements.Count; i++)
			{
				string trimmed = elements[i].Trim();
				if(trimmed == "")
					throw new Exception("Empty array elements are not allowed");

				ret.SetValue(Parse(type.GetElementType(), trimmed), i);
			}

			if (bracketLevel != 0)
				throw new Exception("The number of opening brackets doesn't match the number of closing brackets");

			return ret;
		}

		public class ParserNotFoundException : Exception
		{
			Type type;

			public ParserNotFoundException(Type type)
			{
				this.type = type;
			}

			public override string Message
			{
				get
				{
					return "There's no parser for the type \"" + type.Name + "\"";
				}
			}
		}
	}
}
